Skip to content

menu: Focus a context menu before its first frame - #3422

Merged
madcodelife merged 1 commit into
mainfrom
context-menu-focus-on-open
Oct 9, 2026
Merged

madcodelife merged 1 commit into
mainfrom
context-menu-focus-on-open

Conversation

@madcodelife

@madcodelife madcodelife commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Closes #3364

Description

With accessibility active, opening a ContextMenu aborts a debug build with set_focus called more than once in a single frame.

GPUI registers the focused element as the frame's accessibility focus while prepainting it, and allows one per frame. ContextMenu moved focus to its menu inside the menu's own prepaint. The menu is deferred and prepaints last, so the element that held focus before (for example the right-clicked input) had already registered, and the menu registered a second time.

  • ContextMenu now focuses the menu in the deferred callback that builds it, before its first frame is drawn, after the input selection ownership from input: Preserve selection, focus ring and context menu actions #3382 is resolved. DeferredMenu::build_menu no longer touches focus.
  • The old prepaint focus ran on every frame and pulled focus back whenever it left the menu. That also covered a case it was never written for: entering a submenu with the keyboard and then pointing at another parent item closes the submenu while it still holds focus, leaving Escape and the arrow keys with nowhere to go. PopupMenu now takes focus back itself when a hover closes a focused submenu, which also applies to DropdownMenu and AppMenuBar.

Behavior change: while a context menu is open, focus that the application moves elsewhere is no longer pulled back to the menu on the next frame.

How to Test

cargo test -p gpui-component --lib menu::
cargo test -p gpui-kit --features test-support --test menu
  • menu_takes_focus_before_its_first_frame checks that focus has left the previous element when the frame that draws the menu begins rendering. It fails on main.
  • hovering_away_from_a_keyboard_entered_submenu_refocuses_its_parent enters a submenu with the keyboard, hovers another parent item, and checks that Escape still dismisses the menu. It passes on main, fails with only the ContextMenu change, and passes with this PR.

The headless test platform cannot activate the accessibility tree, so the tests cover the focus ordering that causes the panic rather than the panic itself. This has not been run against a screen reader on a native debug build.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific)

@madcodelife
madcodelife merged commit 9c4b175 into main Oct 9, 2026
15 checks passed
@madcodelife
madcodelife deleted the context-menu-focus-on-open branch October 9, 2026 06:54
linruohan pushed a commit to linruohan/gpui-component that referenced this pull request Oct 9, 2026
Closes longbridge#3364

## Description

With accessibility active, opening a `ContextMenu` aborts a debug build
with `set_focus called more than once in a single frame`.

GPUI registers the focused element as the frame's accessibility focus
while prepainting it, and allows one per frame. `ContextMenu` moved
focus to its menu inside the menu's own prepaint. The menu is deferred
and prepaints last, so the element that held focus before (for example
the right-clicked input) had already registered, and the menu registered
a second time.

- `ContextMenu` now focuses the menu in the deferred callback that
builds it, before its first frame is drawn, after the input selection
ownership from longbridge#3382 is resolved. `DeferredMenu::build_menu` no longer
touches focus.
- The old prepaint focus ran on every frame and pulled focus back
whenever it left the menu. That also covered a case it was never written
for: entering a submenu with the keyboard and then pointing at another
parent item closes the submenu while it still holds focus, leaving
Escape and the arrow keys with nowhere to go. `PopupMenu` now takes
focus back itself when a hover closes a focused submenu, which also
applies to `DropdownMenu` and `AppMenuBar`.

Behavior change: while a context menu is open, focus that the
application moves elsewhere is no longer pulled back to the menu on the
next frame.

## How to Test

```bash
cargo test -p gpui-component --lib menu::
cargo test -p gpui-kit --features test-support --test menu
```

- `menu_takes_focus_before_its_first_frame` checks that focus has left
the previous element when the frame that draws the menu begins
rendering. It fails on `main`.
- `hovering_away_from_a_keyboard_entered_submenu_refocuses_its_parent`
enters a submenu with the keyboard, hovers another parent item, and
checks that Escape still dismisses the menu. It passes on `main`, fails
with only the `ContextMenu` change, and passes with this PR.

The headless test platform cannot activate the accessibility tree, so
the tests cover the focus ordering that causes the panic rather than the
panic itself. This has not been run against a screen reader on a native
debug build.

## Checklist

- [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [x] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)

Co-authored-by: Claude Opus 5.5 <[email protected]>
@huacnlee huacnlee mentioned this pull request Oct 12, 2026
huacnlee added a commit that referenced this pull request Oct 12, 2026
## Description

Prepare v0.7.2 on `stable` from the PRs merged into `main` since v0.7.1,
leaving out every change that breaks existing consumers. `stable`
already carries #3371 and #3373; this adds the remaining compatible PRs
with `git cherry-pick -x` and bumps the workspace to 0.7.2.

### Included

- kit: Add optional gpui-fast backend (#3375)
- tooltip: Make show delay and grace period configurable with
`TooltipDefaults` (#3376)
- docs: Explain recovering focus when the focused element stops
rendering (#3386)
- diff: Add readonly patch viewer (#3378)
- kit: Use one Windows manifest with GPUI Fast (#3380)
- test: Make Progress and TitleBar findable in UI tests (#3383)
- input: Preserve selection, focus ring and context menu actions (#3382)
- diff: Add a Git history example and complete review APIs (#3388)
- text_view: Ignore gestures that stay inside one block plugin (#3390)
- text_view: Round the first and last table rows to the frame radius
(#3309)
- menu: Open a context menu with a long press on touch (#3393)
- text_view: Keep narrow table columns on one line (#3399)
- settings: Keep focus rings inside the list viewport (#3394)
- scrollable_mask: Keep rounded scroll-table corners clean at any scroll
offset (#3400)
- dialog: Add margin_top to AlertDialog (#3404)
- input: Add InputState::add_selection for programmatic multi-cursor
(#3403)
- text_selection: Stop writing selection state on every frame (#3407)
- table: Make the sort icon a labelled button (#3396)
- table: Report visible row ranges of one row or none (#3397)
- dialog: Support configurable entrance animations (#3405)
- docs: Explain the open_dialog builder and footer contracts (#3406)
- dock: Hand the focus to the tab that replaces a closed one (#3409)
- dropdown_button: Add A11y names for the menu trigger (#3410)
- text, resizable: Stop notifying state that did not change (#3418)
- editor: Add on-demand completion commands (#3411)
- table: Clear a right-clicked cell's outline on the next click (#3413)
- tab: Scroll a clicked tab into view with the next one peeking (#3419)
- table: Allow a header row height separate from the body (#3420)
- menu: Focus a context menu before its first frame (#3422)
- text: Preserve logical selection across presentation changes (#3421)
- text: Keep inline HTML mark highlights in Markdown (#3425)
- input: Anchor completion/code-action popups to the current frame's
caret (#3412)
- input: Run custom context menu builders after releasing the input
(#3433)
- input: Keep auto-grow rows fitted when the limits change (#3430)
- text: Paint TextView selection under the glyphs (#3439)

Already on `stable`: #3371, #3373.

### Excluded (Breaking Changes)

- #3432 motion: `Easing::sample` now returns values outside `[0, 1]` for
overshooting curves.
- #3415 tab: new `TabVariant::Folder` breaks exhaustive matches.
- #3402 kit: pointer helpers now keep held modifiers; nested
context-menu behavior changes in tests.
- #3384 test: `TestWindowExt` gains required methods.
- #3302 resizable: `ResizeHandle::on_drag` takes `&T` instead of
`Rc<T>`.
- #3389, #3395, #3398 webview: wrapper API changes, `gpui-wry` →
`gpui-webview` rename, and the GPUI Fast requirement.

#3411, #3405 and #3396 have a Breaking Changes section that reads "None"
and are included. #3376 only deprecates `TooltipRequest::placement`, and
#3397 / #3382 change behavior compatibly, so they are included too.

### Adaptations

- `crates/kit/tests/collections.rs` and `crates/kit/tests/rendering.rs`:
conflict resolutions drop the tests that belong to #3402 and keep each
backported PR's own test.
- `test: Keep backported tests on the v0.7.1 test API`: the menu and
completion tests from #3422 and #3412 used `find_all` and
`pointer_down`/`pointer_up` from #3384/#3402. They now query
`gpui_kit::base::test_support::snapshots` and dispatch mouse events
directly.
- `kit: Prepare v0.7.2`: workspace version bump; `Cargo.lock` only
updates workspace members.

## Public API

No new public API beyond what the included PRs add; each lists its items
in its own `## Public API` section (notably #3375, #3376, #3378, #3388,
#3403, #3404, #3405, #3411, #3420).

## How to Test

- `cargo clippy --workspace --all-targets -- --deny warnings` passes on
Linux.
- `cargo test -p gpui-kit --features "test-support component assets"
--test collections --test menu --test input` passes on Linux.
- The macOS-only `rendering` test (#3439 adds one case, the count is
updated to 11) was not run.

AI-assisted: the backport, conflict resolution and test adaptations were
done with Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Floyd Wang <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Gustave <[email protected]>
Co-authored-by: luren <[email protected]>
Co-authored-by: Ylin <[email protected]>
Co-authored-by: ihavecoke <[email protected]>
Co-authored-by: Lorenzo <[email protected]>
Co-authored-by: MR. XIE <[email protected]>
Co-authored-by: xxww0098 <[email protected]>
Co-authored-by: Sola 🌙🤖 <[email protected]>
Co-authored-by: Sola <[email protected]>
Co-authored-by: Andy Hsu <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Floyd Wang <[email protected]>
Co-authored-by: Daiki Tomioka <[email protected]>
Co-authored-by: Henrik Øgård <[email protected]>
Co-authored-by: Henrik Øgård <[email protected]>
Co-authored-by: Endless <[email protected]>
Co-authored-by: John Costa <[email protected]>
Co-authored-by: Alexander <[email protected]>
Co-authored-by: Cesar Talén <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ContextMenu: accessibility focus assertion aborts native debug builds during prepaint

1 participant